home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
REFERENC
/
TPR
/
TPR3B.TXT
< prev
next >
Wrap
Text File
|
1992-10-19
|
63KB
|
1,625 lines
Chapter 3
- continued -
- Part 2 of 3 parts -
of the
Turbo Pascal Reference
The Turbo Pascal Language
This chapter is part of the Turbo Pascal Reference electronic freeware book (C)
Copyright 1992 by Ed Mitchell. This freeware book contains supplementary
material to Borland Pascal Developer's Guide, published by Que Corporation,
1992. However, Que Corporation has no affiliation with nor responsibility for
the content of this free book. Please see Chapter 1 of the Turbo Pascal
Reference for important information about your right to distribute and use this
material freely. If you find this material of use, I would appreciate your
purchase of one my books, such as the Borland Pascal Developer's Guide or
Secrets of the Borland C++ Masters, Sams Books, 1992. Thank you.
Pointers and Complex Data Structures
Turbo Pascal provides a large variety of built-in data types. However, to
create specific data structures such as list, queues, stacks, trees and so on,
requires that you create and maintain the appropriate data structures yourself.
Pointers are used extensively to create these types of data structures.
Figure 3.4 illustrates a list structure containing a list of filenames.
Each filename is stored in a record, together with pointers to the next and
previous items in the list.
***03tpr04.pcx***
Figure 3.4. Pointers are often used in record structures to create list data
structures, as shown here. In a list, each element is linked, via a pointer,
to another element in the list.
Such a list structure is represented as a Pascal record, containing space
for the filename and other information, plus pointer values to the next and
previous list entries. Listing 3.6 shows a sample data record declared as the
type TListEntry. A pointer to TListEntry is defined as PListEntry.
Listing 3.6. A sample data record set up for use in a list data structure.
Note the use of the separate PListEntry, defined as a pointer to TListEntry.
type
{ Data record to create the list structure }
PListEntry = ^TListEntry;
TListEntry = record
DirInfo : SearchRec;
Next : PListEntry;
Previous: PListEntry;
end; {TListEntry}
A list is constructed out of these records by creating a pointer to the first
item in the list, and storing the first pointer in a variable called ListHead,
and using New ( PListEntry ) to create each entry. The variable ListTail
points to the last item in the list.
var
ListHead : PListEntry;
ListTail : PListEntry;
The Next field of the TListEntry record is used to point to the next
succeeding item in the list. Each time an item is added to the list, the
previous item's Next field is set to point to the new item, and the new item's
Next field, if its the last item in the list, is set to nil to mark the end of
the list.
The Previous field is used to link the list of items in both directions.
In this way, the items in the list can be accessed in both the forward and the
backwards directions.
Listing 3.7 presents a complete sample list program that reads the names
of the files from the current subdirectory and places them into a list data
structure. The program then displays the list in both the forward and backward
directions, using the Next or Previous pointer to reach the next or previous
element in the list structure.
Listing 3.7. This program uses pointers to create and manipulate a list data
structure.
1 program DemoList;
2 {
3 Demonstrates the use of pointers to create a list structure, demonstrates
how
4 list traversal is done in both forwards and backwards directions, and
provides
5 routines to add (or insert) and delete items in the list.
6
7 You can modify these routines for use as a general purpose list
manipulation
8 tool, by changing the ListEntry data structure to hold other types of
data.
9
10 This demonstration program uses the Dos library routines FindFirst and
11 FindNext to read the default file subdirectory.
12 }
13 uses Dos;
14
15 type
16 { Data record to create the list structure }
17 PListEntry = ^TListEntry;
18 TListEntry = record
19 DirInfo : SearchRec;
20 Next : PListEntry;
21 Previous: PListEntry;
22 end; {TListEntry}
23
24 var
25 ListHead : PListEntry;
26 ListTail : PListEntry;
27
28
29 function LowerCase (S : String ) : String;
30 Var
31 I : Integer;
32 begin
33 for I := 1 to length(s) do
34 if ((S[I]>='A') and (S[I]<='Z')) then
35 S[I] := Chr( Ord( S[I] ) + 32 );
36 LowerCase := S;
37 end;
38
39
40
41 procedure InitDirectoryList;
42 { Initialize the directory list structure.
43 For convenience, the first entry contains the default volumne name C:\.
44 }
45 begin
46 ListHead := New(PListEntry);
47 ListHead^.Next := NIL;
48 ListHead^.Previous := NIL;
49 ListTail := ListHead;
50 ListHead^.DirInfo.Name := 'C:\';
51 end; {InitDirectoryList}
52
53
54
55 function AddEntry ( Location : PListEntry;
56 Var ListEntry : SearchRec ) : PListEntry;
57 Var
58 NewEntry : PListEntry;
59 SavedNext : PListEntry;
60
61 begin
62 NewEntry := New ( PListEntry );
63 NewEntry^.DirInfo := ListEntry;
64
65 If Location = ListTail Then
66 {Adding an item on to the tail of the list}
67 begin
68 NewEntry^.Next := NIL;
69 NewEntry^.Previous := ListTail;
70 ListTail^.Next := NewEntry;
71 ListTail := NewEntry;
72 end
73 else
74 {inserting an item within the list}
75 begin
76 SavedNext := Location^.Next;
77 Location^.Next := NewEntry;
78
79 NewEntry^.Next := SavedNext;
80 NewEntry^.Previous := Location;
81
82 SavedNext^.Previous := NewEntry;
83
84 end;{begin}
85
86 AddEntry := NewEntry;
87
88 end;{AddEntry}
89
90
91
92 function RemoveEntry ( Location : PListEntry;
93 HowMany : Integer ) : PListEntry;
94
95 { Starting at the point in the list indicated by 'Location', delete
96 'HomeMany' entries from the list.
97 Return: A pointer to the first item after those that were deleted.
98 }
99
100 var
101 CountOfItems : Integer;
102
103 function DeleteEntry ( Location : PListEntry ) : PListEntry;
104 begin
105 if Location <> NIL then
106 begin
107 If Location^.Previous <> NIL Then
108 Location^.Previous^.Next := Location^.Next;
109 If Location^.Next <> NIL Then
110 Location^.Next^.Previous := Location^.Previous;
111 DeleteEntry := Location^.Next;
112 If Location = ListTail Then
113 ListTail := Location^.Previous;
114 Dispose(Location);
115 end
116 else
117 DeleteEntry := NIL;
118 end;
119
120 begin {RemoveEntry}
121 For CountOfItems := 1 to HowMany Do
122 Location := DeleteEntry ( Location );
123 RemoveEntry := Location;
124 end;{RemoveEntry}
125
126
127 function Move_Fwd ( Location : PListEntry;
128 HowFar : Integer ) : PListEntry;
129 {Starting from 'location' move ahead 'HowFar' items in the list
130 and return the new location
131 }
132 Var
133 I : Integer;
134 begin
135 For I := 1 to HowFar Do
136 If Location^.Next <> NIL Then
137 Location := Location^.Next;
138 Move_Fwd := Location;
139 end;{Move_Fwd}
140
141
142 function Move_Bwd ( Location : PListEntry;
143 HowFar : Integer ) : PListEntry;
144 {Starting from 'location' move backwards 'HowFar' items in the list
145 and return that new location
146 }
147 var
148 I : Integer;
149 begin
150 for I := 1 to HowFar do
151 if Location^.Previous <> NIL Then
152 Location := Location^.Previous;
153 Move_Bwd := Location;
154 end;{Move_Bwd}
155
156
157 Procedure DisplayFwdList;
158 Var
159 TempPtr : PListEntry;
160
161 begin
162 TempPtr := ListHead;
163 While TempPtr <> NIL do
164 begin
165 writeln(TempPtr^.dirinfo.name);
166 tempptr := TempPtr^.Next;
167 end;
168 end;
169
170 procedure DisplayBwdList;
171 Var
172 TempPtr : PListEntry;
173
174 begin
175 TempPtr := ListTail;
176 while TempPtr <> NIL do
177 begin
178 writeln (TempPtr^.dirinfo.name);
179 tempptr := TempPtr^.Previous;
180 end;
181 end;
182
183
184
185 procedure ReadDirectory
186 ( StartingEntry : PListEntry );
187
188 { Purpose:
189 Reads the directory contents and inserts
190 the list into the directory list beginning at 'StartingEntry'.
191
192 }
193 var
194 ListEntry : SearchRec; { Holds the contents of a directory entry
195 consisting of filename, size, etc }
196 CurLocation : PListEntry;
197 IsADirectory : Boolean;
198
199 begin
200 {Call FindFirst to locate all files. The '*.*' matches all filenames,
201 In this case we want to see ALL files so we use the AnyFile mask.
202 Note that for the purpose of this example program we are not doing
203 error checking. We should check the DosError variable after each
204 call to FindFirst and FindNext. Also, its possible that AddEntry
205 will run of memory and return a NIL value but we aren't checking
206 for that in this simplified application example.
207 }
208
209 FindFirst( '*.*', AnyFile, ListEntry );
210 while DosError = 0 do
211 begin
212 if ListEntry.Name[1] <> '.' then
213 {Add all names other than those beginning with '.'. This
214 eliminates our displaying the '.' and '..' names used by DOS}
215 begin
216 IsADirectory := (ListEntry.Attr and Directory) = Directory;
217 if not IsADirectory then
218 ListEntry.Name := LowerCase (ListEntry.Name);
219 {We convert file names to lowercase and leave directory names
220 in upper case for ease of reading the directory listing}
221 StartingEntry := AddEntry ( StartingEntry, ListEntry );
222 end; { begin }
223 FindNext( ListEntry );
224 end; { begin }
225 end; { ReadDirectory }
226
227 begin
228
229 InitDirectoryList;
230
231 ReadDirectory ( ListHead );
232
233
234 DisplayFwdList;
235 Readln;
236
237 DisplayBwdList;
238 Readln;
239
240 end.
Pointers and the With Statement
As with the record statement, a pointer to a record structure can use the
with statement to abbreviate the number of identifiers that need to be written.
For example,
type
{ Data record to create the list structure }
PListEntry = ^TListEntry;
TListEntry = record
DirInfo : SearchRec;
Next : PListEntry;
Previous: PListEntry;
end; {TListEntry}
var
PersonInfo : PListEntry;
...
New ( PersonInfo );
with PersonInfo^ do
begin
DirInfo := DataRecord;
Next := NextPointer;
end;
...
Turbo Pascal Arithmetic Operations
Pascal provides each of the following data and arithmetic operations:
Basic arithmetic operations: +, -, div, *, /, mod, unary + and -
Relational operations: <, <=, =, >=, >, <>
Logical or bit-level operations: and, or, xor, not, shl, shr
Boolean operations: and, or, not, xor
String operations
In addition, through procedures and functions available from the Turbo Pascal
libraries, additional features, including trigonometric, logarithmic, square
root and other functions are available. These functions are described in
chapter 5, "The System Library Reference", in this freeware book. Chapter 4
of the Turbo Pascal Reference includes definitions that you can use for
arc-cosine and arc-sine, two popular functions that are, for unknown reasons,
omitted from Borland's System Library.
Basic Arithmetic Operators
Constants or variables are combined in arithmetic statements to calculate
new values. Turbo Pascal's built-in arithmetic operators are:
+ Addition: A + B to compute A plus B.
- Subtraction: A - B to subtract B from A.
* Multiplication: A * B to multiple A times B.
/ Real number division: A / B to compute A divided by B.
div Truncated integer division: A div B to compute A divided by B and
truncated the result towards zero. For positive results, the truncation
is towards the next lower integer; for negative results, the truncation
is towards the next highest integer. Example: If the result is 3.7,
the A div B produces 3.
mod Modulus: A mod B returns the remainder of the integer division
between A div B and is equivalent to A - ((A div B) * B). Example: 10
mod 3 equals 1.
When operators are mixed in an expression, such as A+B*C, the algebraic rules
of hierachy apply, and B*C is evaluated first, before adding B. Turbo Pascal's
expression evaluation ordering is described below in the section titled
Evaluation Hierarchy.
Note that division is indicated with the div operator for dividing Integer
values, and the / division symbol when dividing Real values.
Mixing Data Types in Expressions
As a general rule, data types should be used consistently in an
expression. For example, if Ch is of type Char and R is of type Real, it does
not make sense to write Ch * R, and in fact, is not allowed by Turbo Pascal.
However, it is possible to convert data types from one type to another, both
implicitly and explictly.
If you mix types in an expression in an unacceptable manner, the Turbo
Pascal compiler will issue an Error 26: Type mismatch error.
Many programmer's encounter this error frequently and fortunately, it is not
particularly difficult to fix. If you see this error message, check your
statements very carefullly, being certain that the data types are correct and
that parentheses are placed in the proper locations.
Implicit Type Conversion
When you multiply a Real value times an Integer value, the result is a
Real value that can be assigned to a real typed variable. The conversion of
the Integer value to a Real is implicit whenever at least one item in the
expression is a real value.
Explicit Type Conversion
You can also explicitly force a real type conversion of the data.
Converting an integer to a real can be done by adding 0.0 to the integer, since
the result of an arithmetic operation involving an integer and a real value is
a real value. For instance,
R := (N+0.0) * 4578;
You can convert Char values to Integer type values using a type cast, as in
this example which uses the type Integer like a function,
N := Integer(Ch);
or by referencing the ordinal function,
N := Ord(Ch);
which returns the ASCII code of the character represented by Ch. To convert an
integer value back to a character, you can write,
Ch := Chr(N);
where Chr converts an integer value back to a character type.
Changing the type of an expression with a type cast is limited to ordinal
data types (Char, Byte, Shortint, Integer, Word, Longint) and to pointer types.
A type rast only works when the internal memory size allocations are identical.
Generally, type casting is used when accessing data that is pointed to by an
untyped pointer variable.
To convert a Real value to an Integer, use the Round(R) or Trunc(R)
truncate function. Round will round its parameter up or down to the nearest
whole number. Trunc disgards the fractional part and returns the mantissa.
When converting Real values to Integer, you must insure that the real value
falls within the acceptable range for integers.
Address-of @ operator
Turbo Pascal provides a special operator, the @ symbol, to compute the
memory address of a particular object. For example,
var I : Integer;
...
APointer := @I;
assigns the memory location of I to the variable APointer. This operator is
used often in pointer operations and for passing procedures as parameters to
other procedures and functions.
Comparision or Relational Operators
You can test how one variable or constant is related to another by using a
relational operator. The result of a relational expression is a boolean True
or False. and may be assigned to a Boolean variable, used in if-then statements
for testing a conditional value, and in while and repeat statements. These
statements are described later.
Equality: A = B returns True if A and B have the same value, and A and
B are of any standard type. Returns False if A is not equal to B.
Less than: A < B returns True if A is less than B. In the case of Char
and String values, the comparison is made according to the underlying
ASCII code representation of the characters. Returns False if A is not
less than B.
Greater than: A > B returns True if A is greater than B. Returns False
if A is not greater than B.
Less than or equal: A <= B returns True if A <= B or A is less than B
returns False if A is neither equal to B nor less than B.
Greater than or equal: A >= B returns True if A = B or A is greater
than B. Returns False if A is not equal to B nor greater than B.
Not equal: A <> B returns True if A is not equal to B, and False if
they are equal.
The values represented by A and B may be any valid Turbo Pascal
expression. You may write, for instance,
I > (J+10) * 5;
Since the result of a relational operator is either True or False, you may
choose to assign this value to a declared Boolean variable, such as the boolean
variable B, as shown here,
B := I > (J+10) * 5;
When comparing values, both operands should be compatible types, with the
exception that if one operand is a Real value, then it is permissible for the
other operand to be an Integer type expression. For example, (I*J) < 45.0;
The relational operators = and <>. only, may also be used for comparing
pointers to see if they are exactly equal or not equal, respecitvely. No other
comparisons are allowed directly on pointers (although you can freely compares
the values that the pointers point to).
Logical or Bit Level Operations
Logical operations may be applied to any scalar data type (Byte, Smallint,
Integer, Word, Longint) and perform a bit-wise logical test. The table below
describes each of the logical operators. The data type of the result of a bit
wise test is determined by the type of the operands.
and: A and B produces a resulting bit pattern that has bits set
where the corresponding bits in A and in B are both set. If both
positions have a 1, then the resulting bit in the same position is a 1.
If either position is a zero, then the resulting bit position is also a
zero. Example: If I is an integer having the decimal value 10 (binary
00001010), then I and 8 (binary 00001010) produces 00001000 since only
this bit is set in both values.
or: A or B produces a bit result such that for each bit position in A
or B this a 1, the resulting bit position is set to 1. If either of the
bits in A or B is 1, then the result is also 1. If both bits are zero,
then resulting bit is a zero. Example: 7 or 32 (binary 00000111 or
binary 0001000) produces 00010111.
not: not A inverts each bit in A. 1 becomes 0 and a zero becomes 1.
xor: A xor B produces the exclusive or of A and B. xor is like the or
operation, except that the result of xor is a 1 only when 1 of the
operands is a 1. If both operands are 1 or both operands are 0, then
the result is 0.
shl: A shl <expression> shifts the bits of A to left the number of bits
specified by the value of the <expression>. Example: 0000 0011 shl 3
produces 0001 1000
shr: A shr <expression> shifts the bits of A to right the number of bits
specified by the value of the <expression>. Example: 0101 0010 shr 4
produces 0000 0101.
Boolean Operations
Boolean expressions are written using the following boolean operators:
and: A and B, where A and B are boolean valued expressions, produces
True if A and B are both True, and False if either A or B is False.
or: A or B produces True if either A or B is True, and False only when
both A and B are False.
not: not A returns False if A is True, or returns True if A is False.
xor: A xor B returns True if either A or B is True, but returns False
if both A and B are True or both A and B are False.
Boolean operators are frequently used when testing multiple conditions in a
conditional expression, such as an if-then statement. For example,
if (NumFiles > 10) and (DeleteFiles = 'YES') then ...
evaluates both relational expressions, each of which returns either True or
False, then ands the two boolean values together. If that result is True, the
then part of the statement is executed.
Short-Circuit versus Complete Evaluation
A special feature of Turbo Pascal is the option to use either complete
boolean expression evaluation or short-circuit boolean expression evaluation.
In complete evaluation mode, the entire boolean expression is evaluated before
testing the result. In short-circuit mode, Turbo Pascal can optimize the
expression evaluation, often generating less code and on average, executing in
less time. In the example,
if (NumFiles > 10) and (DeleteFiles = 'YES') then ...
if short-circuit evaluation is used, Turbo Pascal will jump to the next
statement if NumFiles is less than or equal to 10, since regardless of the
following expression, the overall expression can not possibly be True if the
first operand is already False. Short-circuit evaluation is especially useful
when checking to see if an array index is within the array bounds and then, in
the same expression, referencing the index value. For example, in the
statement,
if (Index <= 20) and (DataItem[Index]=0) then ...
short-circuit evaluation ignores the DataItem[Index]=0 relational comparison if
Index is greater than 20. This is a convenient way to check for an out of
bounds condition. Without short-circuit evaluation, you would otherwise have
to write this statement as,
if Index <= 20 then
if DataItem[Index] = 0 then ...
to prevent a possible out of bounds array indexing operation to occur.
Complete evaluation is useful when the expression calls a function which,
in turn, sets some other values elsewhere in the program. In this instance,
the complete evaluation mode insures that the entire expression is evaluated
fully before making a conditional test.
The compiler's default operation is to use the short-circuit style of
expression evaluation. The choice of evaluation methods is made using the $B
compiler directive ($B+ enables complete evaluation, $B- enables short-circuit
evaluation). See Compiler Directives, later in this chapter.
String Operations
The standard data type, String, is a packed array of Char, where the size
of the string is set to 255 by default, plus a length byte, for a total of 256
bytes of memory. By specifying a new string length in brackets, after String,
a different maximum string length may be specified: For example,
var
S1 : String;
S2 : String[80];
S1 defines a default string of length 255; S2 is defined to hold up to a
maximum of 80 characters. A string variable may, and usually does make use of
fewer bytes than its maximum length, however the allocated memory is always
equal to the maximum length, plus 1. Turbo Pascal tracks the string's current
length by storing the number of characters in an invisible leading byte at the
beginning of the string. Since a string is just an array of Char, this puts
the length byte in the zero'th position of the string (for example, S1[0]).
The normal method of referencing a string's length is to call the Length()
function. If S1 contains 'THIS IS A STRING', then Length(S1) returns the
value 16. If the compiler's range checking option is off {$R-}, you can
directly access the string's length byte, as S1[0], which returns a Char typed
value. To convert to an integer value, use Ord(S1[0])). You may also manually
assign a new string length to a string by writing,
S1[0] := Chr( NewLength );
Even though referencing a string's length byte violates normal range checking,
in practice it is a common occurrence in Turbo Pascal programs. And by
default, the Turbo Pascal compiler operates with range checking turned off.
You may concatenate two strings together (that is, add them together to
make a longer string), using the + symbol. If S1 contains 'THIS', you could
write,
S1 := S1 + ' IS A TEST!';
to assign S1 the new value 'THIS IS A TEST'. The operand that is added to the
first string may be a string constant, variable, expression or a character Char
type. The strings are concatenated together up to a maximum of 255 bytes in
length, after which the excess characters are disgarded.
Evaluation Hierarchy
Turbo Pascal expressions can be written with their operands in any order
(such as 3 + 5 * 8). However, the actual evaluation of the expression does not
occur in the order written. Instead the expression's result is computed
according to the standard rules of algebraic notation and hierachy. Elements
of such expressions are evaluated in this order:
The unary not operator has highest precedence,
Expressions within parantheses are evaluated first,
Then, *, /, div, mod, and the and operator,
Then, the unary + or -
Then, +, -, and the or operator
Lastly, the relational operators =, <, >, <>, <=, >= and the set in
operator.
The short-circuit evaluation option (the normal mode of operation) may cause
boolean expressions to terminate their evaluation before the entire expression
is evaluated.
Pascal Statements
Turbo Pascal program statements describe the operations and flow of
execution in the Pascal program. Each program statement consists of a
sequence of keywords, arithmetic expressions and identifiers, terminated by a
semicolon. The maximum length of a Turbo Pascal line is 126 characters.
However, in most instances Pascal statements (between semicolons) may be
extended across multiple lines with the only restriction being that character
string constants must not be broken across lines. If you need to enter a
string constant longer than the maximum 126 character line length allowed by
Turbo Pascal, use the + string concatenation operator and separate the string
into two statements, like this:
S1 := 'This is going to be a really long character string '
+ 'constant that spreads across two lines!';
The Pascal language syntax is often described using a graphic technique
known as syntax diagram or "railroad diagrams" for their resemblence to a
railroad switching yard. In this format, a Turbo Pascal program statement is
described visually. An example syntax diagram for the if-then-else statement
is shown in Figure 3.5.
***03tpr05.pcx***
Figure 3.5. The Pascal syntax diagram for the if-then-else statement.
An alternative notation is to describe this statement as,
if <expression> then <statement>
or
if <expression> then <statement> else <statement>
Both notations are used, where appropriate, in Turbo Pascal Reference.
Program Comments
Program comments are enclosed within "curly brackets" like this,
{ This is a program comment }
The contents of a program comment are, with the exception of compiler
directives, ignored by the compiler. Alternately, you may enclosed comments
using this notation,
(* This is a program comment *)
During the process of developing your Turbo Pascal programs, you will find it
convenient to standardize on one or the other comment statement formats. Most
programmers have gravitated towards use of the curly brackets. An advantage of
consistently using one or the other type is that while developing and testing
sections of your program, you can comment out entire sections of code,
including sections that already contain comments. Listing 3.8 shows an example
using the (* and *) notation to temporarily eliminate a section of the program.
Listing 3.8. Example use of the (* and *) to comment out an entire section of
source code.
(* COMMENT OUT THIS ENTIRE SECTION
if StartEntry <> NIL then
begin
{ We still have more path to traverse }
StartEntry := Move_Fwd( StartEntry, 1);
if StartEntry^.Level <= ThisLevel then
{ this subdirectory isn't open so we can't search any deeper }
SearchFor := NIL
else
if PathName = '' then
{ Return the address of the subdirectory entry }
SearchFor := Move_Bwd( StartEntry, 1 )
else
{Continuing searching down the path. Note that this uses
a recursive function.}
SearchFor := SearchFor ( PathName, StartEntry );
end; {begin }
END OF COMMENTED OUT SECTION *)
Assignment Statements: :=
Syntax:
<variable name> := <expression of the appropriate type>;
Examples:
I := 10;
AllDone := True;
R := 213456.989;
J := I * 5 - N;
OkToPrint := (Answer='Y') and (PrintOption=10);
Description:
A variable is given a value with an assignment statement. Assignment
statements are written as in this example,
R := 1345.78 * I;
The data type of the expression must be compatibile with the data type of
the variable, or using techniques discussed previously, can be explicitly
converted to the appropriate data type. Assignment statements are also used to
return a function result value, by assigning an expression to the function name
as if it was a variable. Functions are described later in this chapter.
Conditional Statements: If-then-else and case
Turbo Pascal has two conditional statements, the if-then-else statement
and the case statement. Both evaluate some condition and execute exactly one
of their possible outcomes selected by the value of the condition. The
if-then-else statement is typically used for testing amongst a small number of
selections, while the case statement compares one value against a large number
of values or a range of values.
The if-then and if-then-else statements
Syntax:
if <expression> then <statement>
and
if <expression> then <statement> else <statement>
Examples:
if InputLine = '***END MARKER' then
begin
Close(F);
Close(OutFile);
end;
if Cos(Angle)*Theta + DeltaValue > 0.78934 then ...
if Outlining then
begin
ThrowAway := FindPath ( Item, OutlineNumbers );
Move( OutlineNumbers[1],
StrBuffer[2], Length( OutlineNumbers ));
Indent := Level*4 + 4;
end
else
Indent := Indent + 2;
Description:
<expression> is any expression returning a boolean True or False value,
ranging from a simple Boolean to a complex relational expression. If the
expression is True, the statement following then is executed next, while if the
expression is False, the then part is ignored. For example,
if AllDone then
Close( InputFile );
In this statement, if AllDone is True, then the InputFile is closed. If
AllDone is False, then the InputFile is left open and the Close statement is
not executed.
<statement> may be any Pascal statement, including a group of statements
nestled between begin and end.
In the if-then-else form, if the expression is True, the then part is
executed, but if False, the else part is executed. if-then and if-then-else
statements are often spread across multiple lines to improve readability. For
example,
if (Index <= MAXENTRIES) then
XArray[Index] := DataItem
else
Writeln('The list is now full and cannot hold more entries');
The <statement> part of an if-then or if-then-else may itself contain
additional if statements. For example,
if condition1 then
if condition2 then
<statement>
else
<statement>
When if-then-else statements are nested like this, the else binds together with
the most recent if-then. To use a different ordering, you may optionally
enclose the statement within begin-end, like this:
if condition1 then
begin
if condition2 then
<statement>
end
else
<statement>
The case Statement
Syntax:
case <expression> of
<constant> : <statement>;
<constant> : <statement>;
<constant> : <statement>;
...
else
<statement>
end;
The else part is optional. <constant> may specify a range of values using ".."
notation, as for example, "1..10" to specify values in the range of 1 to 10.
Examples of case statements:
case EnteredChar of
'a'..'z' : EnteredChar := Chr( Ord(EnterChar) - 32 ); { convert to lower
case }
'X' : DoExitCommand;
'O' : DoOpenFile;
else
writeln('Command entered is not recognized.');
end;
case Index of
-32768..0: Writeln('Must use a positive valued index.');
1..10 : DoIndexProcessing (Index);
11..15: DoSpecialOperation (Index);
16..32767: Writeln('Index must be between 1 to 15.');
end;
Description:
The case statement contains an initial expression called the selector,
which is used to choose from among a list of various outcomes. When the
selector matches an item in the list, the corresponding statement is executed.
Only one match per case statement is permitted.
The selector <expression> is any expression evaluating to a simple byte or
word-sized result, such as an integer, word or char data type.
The <constant> value should correspond to the type of the selector
expression and may be either a single value or a range of values. A value
range is written as,
100..199:
where .. is placed between the start and end values for the range.
Each <statement> may be any Turbo Pascal statement including a group of
statements nested inside begin and end.
The else part of a case statement is optional, and specifies what to do in
the event that no value in the case list matches the selector. If there is no
match in the case list and there is no else part, then no statements are
executed.
Looping Statements: For, While and Repeat
Looping statements provide a mechanism for repeatedly executing
one or more program statements. Turbo Pascal provides 3 looping
constructs: the for loop, the while loop and the repeat-until loop.
The for loop
Syntax:
for <control variable> :=
<starting expression> to <ending expression> do <statement>
and
for <control variable> :=
<starting expression> downto <ending expression> do
<statement>
Examples:
for I := 1 to 10 do
Writeln('Count = ', I);
{ Prints the alphabet from A to Z }
For Ch := 'A' to 'Z' do Write(Ch);
{ Convert string S to lower case letters }
for I := 1 to length(S) do
if ((S[I]>='A') and (S[I]<='Z')) then
S[I] := Chr( Ord( S[I] ) + 32 );
Description:
The for loop repeatedly executes a sequence of
statements while incrementing or decrementing a loop control variable
during each repetition. ntrol variable> must be a simple ordinal type
(Byte, Char, Smallint, Integer, Word or Longint) variable (not an
array element, record field or real typed) defined locally to a
procedure or function, or a global value only for loops located in the
main body of the program. The control variable is given an initial
value specified by <starting expression>.
If the control variable is within the range specified by the
<starting expression> and the <ending expression>, then the
<statement> part is executed. The <statement> part may include a group
of statements between begin and end. he statements have executed, the
control variable is incremented (or decremented in the case of the
downto). If the new value of the control variable is still within the
range of the starting and ending expression, the loop is executed
again. On ce the control variable falls outside the range of the
starting and ending expressions, program control resumes at the point
immediately after the for statement. the loop, the control variable
should be treated as read only meaning that you must not assign a new
value to the control variable. While the compiler allows you to assign
a new value, the result of this operation is undefined and should not
be u sed. Similarly, the value of the control variable is undefined
after the loop has completed and it should not be relied upon in any
future expressions or conditional expressions. However, if a goto
statement (see below) transfers control out of the for loop you may
then reference the control variable outside the scope of the loop.
While Loop
Syntax:
while <expression> do <statement>
Examples:
while not Eof(InputFile) do
begin
Readln(InputFile, InputLine);
SaveLine(InputLine);
end;
while (I>1) and (S[I-1] <> ' ') do
begin
Insert (',', S, I);
I := I - 3;
end;
Description:
The while loop repeatedly executes a statement or group of statements as
long the control expression is True. The control expression must always
return a boolean True or False value. As soon as the expression is False, the
loop terminates and the program resumes execution at the statement after the
while loop.
The while loop always evaluates the expression at the beginning of the
loop. (See also the repeat-until loop for a loop that evaluates the expression
at the end of the loop.)
Repeat Loop
Syntax:
repeat <statement> until <expression>
Examples:
I := 0;
repeat
I := I + 1;
Writeln(I);
I := I + 1;
until I = 10;
{ Keep reading keyboard commands until user selects a Done
function }
MenusDone := False;
repeat
MenuCommand := GetKey;
Command := MenuCommand; {Set up default value for
return'd command}
case MenuCommand of
KEY_LEFTARROW:
begin
...
end; { begin }
...
KEY_ENTER:
begin
MenuPtr := FindItem(CurrentMenu, MenuIndex);
Command := MenuPtr^.CmdCode;
MenusDone := True;
end; { begin }
KEY_ESCAPE: MenusDone := TRUE;
else begin {all other keys} end;
End; { case }
Until MenusDone;
Description:
repeat-until repetitively executes one or more statements appearing
between the repeat and the until keywords. The statements are executed as long
as the conditional expression after the until is False. When the condition
evaluates to True, the loop terminates and program execution resumes at the
statement following until.
The repeat-until statement always executes the loop at least once since
the conditional test is not performed until reaching the until statement.
Unlike the other looping statements, the repeat and until keywords suffice
to delineate multiple statements, hence, you do not need to enclose multiple
statements with begin-end.
Labels and Goto
Goto Syntax:
goto <label>
Example goto:
goto 100;
Label declaration Syntax:
label <list of labels>
Example Label declaration:
label 100, 200, 300;
Label usage Syntax:
<label>:
Example label usage:
100:
Example use of Label and Goto:
procedure LabelDemo;
label 999;
var I : Integer;
begin
for I := 1 to MAXLINES do
begin
Writeln(Outfile, Lines[I] );
if IoResult <> 0 then
begin
Writeln('Error occurred while writing output
file.');
goto 999;
end;
end;
999: Close(OutFile);
end;
Description:
The goto statement transfers program execution directly to some other
location in the program specified by a label, where <label> is an unsigned
integer up to 4 digits in length and is located somewhere within the program or
program unit. Each label must be explicitly declared in a label declarations
section at the beginning of the program, unit, procedure or function, just like
any other identifier. The actual location of the label is determined by
placing the label, followed by a colon, somewhere within the program text.
A label and a goto statement are sometimes used within procedures to
quickly transfer program execution to the end of the procedure. An error
condition or a user input request to finish up might prompt an early completion
to execution of a sequence of statements. The example label, shown as "999" in
the example above, must be declared before use with the label statement, and
then is positioned within the program text by writing "999:" at the beginning
of a statement. The label is referenced by the goto 999 statement.
Important note: Do not use Goto to jump into a lower level block
Do not use goto and a label to jump into the middle of a looping
construct, such as,
for I := 1 to 30 do
begin
1: {Don't jump to the middle of a loop}
...
end;
The compiler will not generate an error if you compile such code, however,
the result is potentially random. Never jump into a deeper nesting level, only
to the same level or to a higher level.
The label and the goto statement that refers to it, should always be in
the same program block. That means you can not use goto to jump from within
one procedure or function into another procedure or function.
Lastly, do not place a label directly after a then statement, such as,
if I = 10 then 1: ...
although you may place a label within a begin-end block that follows a then
statement.
Procedures and Functions
Few Pascal programs consist of a single main program body. Most are split
into subtasks or subroutines, called procedures (or their close cousin, the
function). The use of procedures and functions is an essential part of Turbo
Pascal programming, and through appropriate use of procedures you can use
structured program techniques to create modular programs that are more reliable
and easier to maintain.
Procedures provide a method of isolating portions of your program into
separate, callable routines, which you can call or activate from anywhere
within your program. Procedures may have a list of parameters, which are used
to pass values into the procedure, and to return results.
Functions are like procedures, except that a function is called from
within an expression and directly returns a result to be used in evaluating the
expression.
Turbo Pascal provides enhancements to the basic Pascal procedure to
provide for support of the underlying 80x86 microprocessor, and the creation of
Pascal language interrupt handlers and assembly language interfacing. These
features are specified with the keywords, near, far, and interrupt. Assembly
language statements are incorporated directly into the Pascal source using
either the asm or inline facilities.
Procedures
Syntax:
procedure <identifier> <optional parameter list> <procedure body>
The <optional parameter list> defines the values and data types that may
be passed to the procedure at the time the procedure is called, and if the
procedure may return values to the caller. Parameter lists are described in
more detail, below.
The <procedure body> normally consists of a paired begin-end statement,
enclosing zero or more program statements. In Turbo Pascal, the <procedure
body> may be optionally prefaced with:
near; Use the 80x86 near procedure calling convention.
far; Use the 80x86 far calling convention.
interrupt; For creating special interrupt handler procedures.
library; To create a dynamic link library procedure. See
Chapter 2, "Units and Dynamic Link Libraries" in
the Borland Pascal Developer's Guide, Que, 1992.
Instead of the normal begin-end statement block, you may write:
forward;
to specify that the procedure body is specified later in the
program.
Use external when the procedure body is defined in a separately compiled
object module that has been written in assembly language.
Use inline() to incorporates machine code directly into the compiled code,
rather than generating a call to the procedure. See Chapter 6, "Assembly
Language Programming" in the Borland Pascal Developer's Guide.
The <Optional parameter list>
Parameters to a procedure or function are specified within parentheses,
after the procedure or function identifier. The parameter list specifies one
or more parameters, their type, and if the parameter is passed by value (a
value parameter) or by reference (a variable parameter). When defining the
parameter list, you create a list of variables (similar to a var declaration)
assigning an identifier and data type to each parameter, and specifying whether
the parameter is a value parameter or a variable parameter. For example,
procedure PrintSum (A, B : Integer);
begin
Writeln('Sum of ',A,' + ',B, ' = ',A + B);
end;
defines a procedure PrintSum having two integer parameters A and B, both passed
by value. The parameters A and B become local variables to the procedure
PrintSum, and are undefined outside the scope of the procedure.
To use this procedure, you call the procedure by placing the procedure
name and the values for the parameters in a statement, like this:
begin
PrintSum( 10, 20 );
which results in this output:
Sum of 10 + 20 = 30
The first parameter value, 10, is assigned to the parameter variable A, and the
second parameter value, 20, is assigned to the parameter variable B. In the
case of value parameters such as A and B, you can use any expression, which
will be evaluated during program execution at the time of the procedure call.
For example,
PrintSum( 10+30, 5*TotalSize );
The expression 10+30 will evaluate to 40, and A will get the value of 40.
Variable parameters provide a way for the procedure to modify the contents
of a variable passed to it. For example, by redefining PrintSum as shown
below, the variable C becomes a var variable parameter. Inside PrintSum, C is
assigned the result of A+B.
procedure PrintSum( A, B: Integer; var C: Integer);
begin
Writeln('Sum of ',A,' + ',B, ' = ',A + B);
C := A + B;
end;
In this way, when PrintSum is called like this,
PrintSum( 10+30, 40, AValue );
the local variable C gets assigned the sum, 80. The variable passed as a
parameter to PrintSum, AValue, also gets assigned the sum, 80. A parameter
defined as a var type, can only have a matching variable passed to it as a
parameter, so that it can return a value. You must not try to pass an
expression to a matching variable parameter, only to a value parameter.
Arrays as Parameters
Arrays and array elements may be passed to procedures as value parameters
or as variable parameters.
To pass an individual array element requires only that the parameter
expression match the data type of the defined value parameter. For example,
var
AnArray : Array[1..10] of Integer;
procedure P ( X : Integer );
begin
...
end;
...
P ( AnArray[5] );
Array elements may also be passed as variable parameters. For example,
procedure P ( var X : Integer );
begin
...
end;
...
P ( AnArray[5] );
To pass an entire array to a procedure or function parameter requires that
a user defined type be declared to describe the array. For example,
type
TArray : Array[1..10] of Integer;
var
AnArray : TArray;
procedure P ( X : TArray );
begin
...
end;
...
P ( AnArray );
Turbo Pascal requires the parameter's type to be a simple identifier, so you
cannot write:
procedure P ( X : Array[1..10] of Integer );
Instead, you must always define a new type equivalent to the array definition,
and use that to declare array parameters.
The example above passes an array by value. Internally, this is
implemented by copying the entire array onto the stack, and then call the
procedure. Not surprisingly, for large arrays this is both a time and memory
consumer. Instead, for large structures, such as arrays or large records, it
is recommend that you pass arrays as var parameter types, like this:
procedure P ( var X : TArray );
Strings as Parameters
Strings may be passed either by value or as variable parameters. When a
string is passed to a procedure parameter by value, any string expression may
be used at the point of call. The compiler will generate code to evaluate the
expression and then make a copy of the result, which is then passed to the
procedure. In some instances, this extra overhead of making a copy of the
string value may be prohibitive. Instead, you may wish to pass the string as a
variable parameter since the compiler will then reference the original string,
rather than making a copy of the string. For example,
Procedure StringDemo( var S : String );
begin
S := '/' + S + '/';
end;
which is then called with,
var AString : String;
...
AString := 'TOOLS';
StringDemo( AString );
results in AString getting the value, '/TOOLS/'. When using a variable
parameter, you must always pass a variable identifier when the procedure is
called. Using a string expression, such as,
StringDemo('TOOLS');
will result in a compiler error message. Keep in mind that any changes you
make to the parameter variable within the procedure will be reflected in the
string that was passed to the procedure.
Important Note: Mixing String Types and Var Parameters
Normally Turbo Pascal will permit mixing of different string types when
they passed as variable parameters. For instance, if AString is defined as,
var AString : String[30];
then,
StringDemo( AString );
will result in an error because the parameter variable S is defined as type
String, which is equivalent to String[255]. Normally, Turbo Pascal performs
"strict type checking" on parameter strings. This means that the string type
and length must be identical. However, you can disable strict type checking
for var-parameter strings, by using the {$V-} directive to disable
var-parameter string type checking. With {$V-} in effect, Turbo Pascal will
allow you to mix different length strings for var-string parameters. You must
insure that you do not inadvertently access portions of the string parameter
that are out of bounds. See "Compiler Directives" later in this chapter for
more information about the $V directive.
Another standard method of passing strings as var parameters is to create
a user defined type and then to use that type consistently for string
variables. For instance,
type
String80 = String[80];
var
AString : String80;
procedure P ( var StringParam : String80 );
begin
...
end;
Now, when the statement P( AString) is executed, AString has exactly the same
type P's StringParam parameter.
Records as Parameters
To pass a record structure as either a value or variable parameter
requires that you create a user defined data type equivalent to the record's
definition. Example:
type
TPersonInfo = record
Name : String[30];
Phone : String[14];
Age : Integer;
end;
var
PersonInfo : TPersonInfo;
procedure P ( PersonRecord : TPersonInfo );
begin
...
end;
...
P ( PersonInfo );
Summary of Using Parameter Values and Variables
To summarize, parameter variables define a list of local variables to the
procedure, each of which is matched to a parameter value at the time the
procedure is invoked. Parameter variables may be either value parameters or
variable parameters. A value parameter may receive any expression, which will
be evaluated at the time of the procedure call. A variable parameter must only
receive another variable as its parameter, and may be used by a procedure to
return a value to the procedure's caller.
Structured data types must make use of a user defined type to specify the
parameter's data type.
The Procedure Body
Syntax:
<optional variable declarations>
<optional procedure declarations>
begin
statements;
end;
Example:
Listing 3.8. The DrawWindow procedure.
Procedure DrawWindow (X1, Y1, X2, Y2 : Integer );
{ Draws a window for a menu or dialog. Essentially this declares
a text viewport using the Turbo Window procedure and proceeds to
draw a border "around" the window. In this case, the border
occupies one character on all sides of the requested area.}
Var
Width : Integer;
S : String[80];
Y : Integer;
Begin
{Create Window area on the screen}
Window (X1, Y1, X2, Y2+1);
Width := X2-X1+1;
{Draw border around the window}
{First draw the top line across the window border}
FillChar ( S, Width, Chr(196) );
S[0] := Chr(Width);
S[1] := Chr(218);
S[Width] := Chr(191);
WriteStr( 1, 1, S, LightGray, Black );
{Next, Draw the sides of the window border}
FillChar ( S, Width, ' ' );
S[0] := Chr(Width);
S[1] := Chr(179);
S[Width] := Chr(179);
For Y := 2 To Y2-Y1 Do
WriteStr(1, Y, S, LightGray, Black );
{Then Draw the bottom border}
FillChar ( S, Width, Chr(196) );
S[0] := Chr(Width);
S[1] := Chr(192);
S[Width] := Chr(217);
WriteStr( 1, Y2-Y1+1, S, LightGray, Black);
End;
Description:
The body of a procedure may contain additional identifier declarations,
including local procedures and functions that are defined inside the procedure
or function. Such identifiers, including sub-procedures or functions, are
available for use solely within the scope of the outer procedure.
The main body of the procedure consists of the keyword begin, followed by
zero or more Pascal statements, and terminated by the keyword end. Listing 3.8
presents a sample procedure DrawWindow, which displays a rectangular window on
the screen, complete with a border. DrawWindow has 4 parameter variables, X1,
Y1, and X2,Y2 which are integer values describing the screen coordinates of the
upper left and lower right corner of the window. DrawWindow also has 3 local
variables, Width, S, and Y. Both the parameters and the local variables are
all local to procedure DrawWindow meaning that they are not defined nor
available outside the procedure.
Forward declared procedures
Syntax:
procedure <identifier> <parameter list>; forward;
...
procedure <identifier>; <procedure body>
or external or asm keywords.
Example:
The following is an illustration of a forward declared procedure named
DoCommand. DoCommand is declared as forward because it must be called by the
DoStatement procedure; however, DoCommand may also need to call DoStatement so
it would not be possible to define either before the other except to make one a
forward declared procedure.
Listing 3.9. The use of forward declared procedures.
procedure DoCommand ( TheCommand : Word ); forward;
procedure DoStatement ( Location : Integer );
begin
...
if InputLine[Location]= '.' then
DoCommand ( HitPeriod );
end;
procedure DoCommand;
begin
case TheCommand of
OnStatement : DoStatement ( Location );
HitPeriod : DoPeriod;
...
end;
end;
Description:
At times, a procedure identifier needs to be declared early in a program
so that subsequently defined procedures can call it. However, since this
procedure, in turn, needs to call other procedures that have not yet been
defined, the main body of the procedure must be defined after the declaration
of those procedures that it needs to use. Such a procedure is declared using
the forward declaration instead of a procedure block. The actual
implementation then appears later in the program or unit.
The actual definition that appears later is not allowed to be another
forward declaration, nor a near, far or inline declaration. However, it may
contain the external or asm assembly language directives. Further, at the
point that the forward declared procedure is actually implemented, it should
not redefine the parameter list.
In between the procedure's forward declared header and the subsequent
procedure definition, other procedures and functions may be defined. In this
way, procedures can be written that call each other as shown in Listing 3.9,
above.
Near and Far procedure call models
Example:
Example of a procedure declared using the far declaration. This example
uses the Turbo Vision TCollection feature ForEach, which requires a pointer to
a far procedure as a parameter, shown in the PhoneBook^.ForEach( @PrintEntry )
statement.
procedure PrintEntry( OneEntry : PPersonInfo ); far;
begin
with OneEntry^ do
Writeln(Name,Address,City,State,Zip,Age);
end; { PrintEntry }
begin
PhoneBook^.ForEach( @PrintEntry );
end;
Description:
The 80x86 CPU architecture divides memory in to 64k byte chunks called
segments. Procedures may be called within a segment or across segment
boundaries. When a procedure call is made within a segment, it is a near
procedure call. When a procedure call is made across segment boundaries, it is
a far procedure call.
A near procedure call requires fewer machine instructions, hence it
requires less memory and executes slightly faster than a far call. However,
near procedures can only be called from within the same module (or unit) that
they are defined, while a far defined procedure can be called from any code
module.
Turbo Pascal automatically determines if a procedure or function should
use the near or far call memory model. Normally, all procedures or functions
declared in the Interface section of a program unit are far declared. All
procedures or functions defined within the program or implementation section of
a unit are near model calls.
Using the near or far declarations, you can override the automatically
determined procedure call type. Some examples of procedure or functions that
you might override are:
Procedures or functions which are themselves passed as variable
parameters must be declared as far procedures.
Overlay procedures (those that are swappable out of memory) must be far
procedures.
In addition to using the far directive, you can force the compiler to generate
far calls for all procedures and functions by using the {$F+} compiler
directive. The normal state is {$F-}, meaning that the compiler will
automatically choose either near or far as appropriate. In the {$F+} state,
the compiler will always generate far calls.
Interrupt Procedures
Interrupt declarations specify that the procedure is an interrupt handler
procedure. Interrupt handlers in Turbo Pascal are fully explained in Chapter
11. The basic interrupt procedure must follow this format:
procedure Handler
(Flags, CS, IP, AX, BX, CX, DX, SI, DI, DS, ES, BP : Word);
interrupt;
begin
...
end;
The procedure header must be specified as shown, so that all of the CPU
registers may be accessed as if they were parameter variables (even though the
var keyword is not specified).
Interrupt procedures should be activated only by an interrupt condition
and cannot be called from within your Turbo Pascal program as if they were a
normal procedure. Chapter 11 provides additional details and examples of
interrupt procedures.
Assembly language procedures: External, Inline and Asm
Turbo Pascal provides 3 types of assembly language interfacing.
Assembly language routines can be written and assembled using an
external assembler such as Turbo Assembler, and then linked to your
Turbo Pascal programs. Such routines are made visible to the Turbo
Pascal code by defining a corresponding procedure or function header
within your Turbo Pascal source and declaring it as external. The
external assembly language routine is then linked from the assembled
object module.
Turbo Pascal contains a built-in assembler that let's you write complete
assembly language routines without leaving the Turbo Pascal compiler.
Such routines are written using the asm keyword.
Lastly, you can embed values, such as machine code or data values
directly into the generated code using the inline statement.
Turbo Pascal's built-in assembly language features and the Turbo Assembler are
described in Chapter 6, "Assembly Language Programming" in the Borland Pascal
Developer's Guide, Que Books, 1992.